1   /*
2    * Copyright (C) 2008 The Android Open Source Project
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.android.launcher;
18  
19  import android.content.Context;
20  import android.content.res.TypedArray;
21  import android.content.res.Resources;
22  import android.graphics.Rect;
23  import android.graphics.RectF;
24  import android.util.AttributeSet;
25  import android.view.ContextMenu;
26  import android.view.MotionEvent;
27  import android.view.View;
28  import android.view.ViewDebug;
29  import android.view.ViewGroup;
30  
31  import java.util.ArrayList;
32  
33  public class CellLayout extends ViewGroup {
34      private boolean mPortrait;
35  
36      private int mCellWidth;
37      private int mCellHeight;
38      
39      private int mLongAxisStartPadding;
40      private int mLongAxisEndPadding;
41  
42      private int mShortAxisStartPadding;
43      private int mShortAxisEndPadding;
44  
45      private int mShortAxisCells;
46      private int mLongAxisCells;
47  
48      private int mWidthGap;
49      private int mHeightGap;
50  
51      private final Rect mRect = new Rect();
52      private final CellInfo mCellInfo = new CellInfo();
53      
54      int[] mCellXY = new int[2];
55      
56      boolean[][] mOccupied;
57  
58      private RectF mDragRect = new RectF();
59  
60      private boolean mDirtyTag;
61  
62      public CellLayout(Context context) {
63          this(context, null);
64      }
65  
66      public CellLayout(Context context, AttributeSet attrs) {
67          this(context, attrs, 0);
68      }
69  
70      public CellLayout(Context context, AttributeSet attrs, int defStyle) {
71          super(context, attrs, defStyle);
72          TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
73  
74          mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10);
75          mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10);
76          
77          mLongAxisStartPadding = 
78              a.getDimensionPixelSize(R.styleable.CellLayout_longAxisStartPadding, 10);
79          mLongAxisEndPadding = 
80              a.getDimensionPixelSize(R.styleable.CellLayout_longAxisEndPadding, 10);
81          mShortAxisStartPadding =
82              a.getDimensionPixelSize(R.styleable.CellLayout_shortAxisStartPadding, 10);
83          mShortAxisEndPadding = 
84              a.getDimensionPixelSize(R.styleable.CellLayout_shortAxisEndPadding, 10);
85          
86          mShortAxisCells = a.getInt(R.styleable.CellLayout_shortAxisCells, 4);
87          mLongAxisCells = a.getInt(R.styleable.CellLayout_longAxisCells, 4);
88  
89          a.recycle();
90  
91          setAlwaysDrawnWithCacheEnabled(false);
92  
93          if (mOccupied == null) {
94              if (mPortrait) {
95                  mOccupied = new boolean[mShortAxisCells][mLongAxisCells];
96              } else {
97                  mOccupied = new boolean[mLongAxisCells][mShortAxisCells];
98              }
99          }
100     }
101 
102     @Override
103     public void cancelLongPress() {
104         super.cancelLongPress();
105 
106         // Cancel long press for all children
107         final int count = getChildCount();
108         for (int i = 0; i < count; i++) {
109             final View child = getChildAt(i);
110             child.cancelLongPress();
111         }
112     }
113 
114     int getCountX() {
115         return mPortrait ? mShortAxisCells : mLongAxisCells;
116     }
117 
118     int getCountY() {
119         return mPortrait ? mLongAxisCells : mShortAxisCells;
120     }
121 
122     @Override
123     public void addView(View child, int index, ViewGroup.LayoutParams params) {
124         // Generate an id for each view, this assumes we have at most 256x256 cells
125         // per workspace screen
126         final LayoutParams cellParams = (LayoutParams) params;
127         cellParams.regenerateId = true;
128 
129         super.addView(child, index, params);
130     }
131 
132     @Override
133     public void requestChildFocus(View child, View focused) {
134         super.requestChildFocus(child, focused);
135         if (child != null) {
136             Rect r = new Rect();
137             child.getDrawingRect(r);
138             requestRectangleOnScreen(r);
139         }
140     }
141 
142     @Override
143     protected void onAttachedToWindow() {
144         super.onAttachedToWindow();
145         mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this);
146     }
147 
148     @Override
149     public boolean onInterceptTouchEvent(MotionEvent ev) {
150         final int action = ev.getAction();
151         final CellInfo cellInfo = mCellInfo;
152 
153         if (action == MotionEvent.ACTION_DOWN) {
154             final Rect frame = mRect;
155             final int x = (int) ev.getX() + mScrollX;
156             final int y = (int) ev.getY() + mScrollY;
157             final int count = getChildCount();
158 
159             boolean found = false;
160             for (int i = count - 1; i >= 0; i--) {
161                 final View child = getChildAt(i);
162 
163                 if ((child.getVisibility()) == VISIBLE || child.getAnimation() != null) {
164                     child.getHitRect(frame);
165                     if (frame.contains(x, y)) {
166                         final LayoutParams lp = (LayoutParams) child.getLayoutParams();
167                         cellInfo.cell = child;
168                         cellInfo.cellX = lp.cellX;
169                         cellInfo.cellY = lp.cellY;
170                         cellInfo.spanX = lp.cellHSpan;
171                         cellInfo.spanY = lp.cellVSpan;
172                         cellInfo.valid = true;
173                         found = true;
174                         mDirtyTag = false;
175                         break;
176                     }
177                 }
178             }
179 
180             if (!found) {
181                 int cellXY[] = mCellXY;
182                 pointToCellExact(x, y, cellXY);
183 
184                 final boolean portrait = mPortrait;
185                 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
186                 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
187 
188                 final boolean[][] occupied = mOccupied;
189                 findOccupiedCells(xCount, yCount, occupied, null);
190 
191                 cellInfo.cell = null;
192                 cellInfo.cellX = cellXY[0];
193                 cellInfo.cellY = cellXY[1];
194                 cellInfo.spanX = 1;
195                 cellInfo.spanY = 1;
196                 cellInfo.valid = cellXY[0] >= 0 && cellXY[1] >= 0 && cellXY[0] < xCount &&
197                         cellXY[1] < yCount && !occupied[cellXY[0]][cellXY[1]];
198 
199                 // Instead of finding the interesting vacant cells here, wait until a
200                 // caller invokes getTag() to retrieve the result. Finding the vacant
201                 // cells is a bit expensive and can generate many new objects, it's
202                 // therefore better to defer it until we know we actually need it.
203 
204                 mDirtyTag = true;
205             }
206             setTag(cellInfo);
207         } else if (action == MotionEvent.ACTION_UP) {
208             cellInfo.cell = null;
209             cellInfo.cellX = -1;
210             cellInfo.cellY = -1;
211             cellInfo.spanX = 0;
212             cellInfo.spanY = 0;
213             cellInfo.valid = false;
214             mDirtyTag = false;
215             setTag(cellInfo);
216         }
217 
218         return false;
219     }
220 
221     @Override
222     public CellInfo getTag() {
223         final CellInfo info = (CellInfo) super.getTag();
224         if (mDirtyTag && info.valid) {
225             final boolean portrait = mPortrait;
226             final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
227             final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
228 
229             final boolean[][] occupied = mOccupied;
230             findOccupiedCells(xCount, yCount, occupied, null);
231 
232             findIntersectingVacantCells(info, info.cellX, info.cellY, xCount, yCount, occupied);
233 
234             mDirtyTag = false;
235         }
236         return info;
237     }
238 
239     private static void findIntersectingVacantCells(CellInfo cellInfo, int x, int y,
240             int xCount, int yCount, boolean[][] occupied) {
241 
242         cellInfo.maxVacantSpanX = Integer.MIN_VALUE;
243         cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE;
244         cellInfo.maxVacantSpanY = Integer.MIN_VALUE;
245         cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE;
246         cellInfo.clearVacantCells();
247 
248         if (occupied[x][y]) {
249             return;
250         }
251 
252         cellInfo.current.set(x, y, x, y);
253 
254         findVacantCell(cellInfo.current, xCount, yCount, occupied, cellInfo);
255     }
256 
257     private static void findVacantCell(Rect current, int xCount, int yCount, boolean[][] occupied,
258             CellInfo cellInfo) {
259 
260         addVacantCell(current, cellInfo);
261 
262         if (current.left > 0) {
263             if (isColumnEmpty(current.left - 1, current.top, current.bottom, occupied)) {
264                 current.left--;
265                 findVacantCell(current, xCount, yCount, occupied, cellInfo);
266                 current.left++;
267             }
268         }
269 
270         if (current.right < xCount - 1) {
271             if (isColumnEmpty(current.right + 1, current.top, current.bottom, occupied)) {
272                 current.right++;
273                 findVacantCell(current, xCount, yCount, occupied, cellInfo);
274                 current.right--;
275             }
276         }
277 
278         if (current.top > 0) {
279             if (isRowEmpty(current.top - 1, current.left, current.right, occupied)) {
280                 current.top--;
281                 findVacantCell(current, xCount, yCount, occupied, cellInfo);
282                 current.top++;
283             }
284         }
285 
286         if (current.bottom < yCount - 1) {
287             if (isRowEmpty(current.bottom + 1, current.left, current.right, occupied)) {
288                 current.bottom++;
289                 findVacantCell(current, xCount, yCount, occupied, cellInfo);
290                 current.bottom--;
291             }
292         }
293     }
294 
295     private static void addVacantCell(Rect current, CellInfo cellInfo) {
296         CellInfo.VacantCell cell = CellInfo.VacantCell.acquire();
297         cell.cellX = current.left;
298         cell.cellY = current.top;
299         cell.spanX = current.right - current.left + 1;
300         cell.spanY = current.bottom - current.top + 1;
301         if (cell.spanX > cellInfo.maxVacantSpanX) {
302             cellInfo.maxVacantSpanX = cell.spanX;
303             cellInfo.maxVacantSpanXSpanY = cell.spanY;
304         }
305         if (cell.spanY > cellInfo.maxVacantSpanY) {
306             cellInfo.maxVacantSpanY = cell.spanY;
307             cellInfo.maxVacantSpanYSpanX = cell.spanX;
308         }
309         cellInfo.vacantCells.add(cell);
310     }
311 
312     private static boolean isColumnEmpty(int x, int top, int bottom, boolean[][] occupied) {
313         for (int y = top; y <= bottom; y++) {
314             if (occupied[x][y]) {
315                 return false;
316             }
317         }
318         return true;
319     }
320 
321     private static boolean isRowEmpty(int y, int left, int right, boolean[][] occupied) {
322         for (int x = left; x <= right; x++) {
323             if (occupied[x][y]) {
324                 return false;
325             }
326         }
327         return true;
328     }
329 
330     CellInfo findAllVacantCells(boolean[] occupiedCells, View ignoreView) {
331         final boolean portrait = mPortrait;
332         final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
333         final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
334 
335         boolean[][] occupied = mOccupied;
336 
337         if (occupiedCells != null) {
338             for (int y = 0; y < yCount; y++) {
339                 for (int x = 0; x < xCount; x++) {
340                     occupied[x][y] = occupiedCells[y * xCount + x];
341                 }
342             }
343         } else {
344             findOccupiedCells(xCount, yCount, occupied, ignoreView);
345         }
346 
347         return findAllVacantCellsFromOccupied(occupied, xCount, yCount);
348     }
349 
350     /**
351      * Variant of findAllVacantCells that uses LauncerModel as its source rather than the 
352      * views.
353      */
354     CellInfo findAllVacantCellsFromOccupied(boolean[][] occupied,
355             final int xCount, final int yCount) {
356         CellInfo cellInfo = new CellInfo();
357 
358         cellInfo.cellX = -1;
359         cellInfo.cellY = -1;
360         cellInfo.spanY = 0;
361         cellInfo.spanX = 0;
362         cellInfo.maxVacantSpanX = Integer.MIN_VALUE;
363         cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE;
364         cellInfo.maxVacantSpanY = Integer.MIN_VALUE;
365         cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE;
366         cellInfo.screen = mCellInfo.screen;
367 
368         Rect current = cellInfo.current;
369 
370         for (int x = 0; x < xCount; x++) {
371             for (int y = 0; y < yCount; y++) {
372                 if (!occupied[x][y]) {
373                     current.set(x, y, x, y);
374                     findVacantCell(current, xCount, yCount, occupied, cellInfo);
375                     occupied[x][y] = true;
376                 }
377             }
378         }
379 
380         cellInfo.valid = cellInfo.vacantCells.size() > 0;
381 
382         // Assume the caller will perform their own cell searching, otherwise we
383         // risk causing an unnecessary rebuild after findCellForSpan()
384         
385         return cellInfo;
386     }
387 
388     /**
389      * Given a point, return the cell that strictly encloses that point 
390      * @param x X coordinate of the point
391      * @param y Y coordinate of the point
392      * @param result Array of 2 ints to hold the x and y coordinate of the cell
393      */
394     void pointToCellExact(int x, int y, int[] result) {
395         final boolean portrait = mPortrait;
396         
397         final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
398         final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
399 
400         result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
401         result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
402 
403         final int xAxis = portrait ? mShortAxisCells : mLongAxisCells;
404         final int yAxis = portrait ? mLongAxisCells : mShortAxisCells;
405 
406         if (result[0] < 0) result[0] = 0;
407         if (result[0] >= xAxis) result[0] = xAxis - 1;
408         if (result[1] < 0) result[1] = 0;
409         if (result[1] >= yAxis) result[1] = yAxis - 1;
410     }
411     
412     /**
413      * Given a cell coordinate, return the point that represents the upper left corner of that cell
414      * 
415      * @param cellX X coordinate of the cell 
416      * @param cellY Y coordinate of the cell
417      * 
418      * @param result Array of 2 ints to hold the x and y coordinate of the point
419      */
420     void cellToPoint(int cellX, int cellY, int[] result) {
421         final boolean portrait = mPortrait;
422         
423         final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
424         final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
425 
426 
427         result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
428         result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
429     }
430 
431     @Override
432     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
433         // TODO: currently ignoring padding
434         
435         int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
436         int widthSpecSize =  MeasureSpec.getSize(widthMeasureSpec);
437         
438         int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
439         int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);
440         
441         if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
442             throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
443         }
444 
445         final int shortAxisCells = mShortAxisCells;
446         final int longAxisCells = mLongAxisCells;
447         final int longAxisStartPadding = mLongAxisStartPadding;
448         final int longAxisEndPadding = mLongAxisEndPadding;
449         final int shortAxisStartPadding = mShortAxisStartPadding;
450         final int shortAxisEndPadding = mShortAxisEndPadding;
451         final int cellWidth = mCellWidth;
452         final int cellHeight = mCellHeight;
453 
454         mPortrait = heightSpecSize > widthSpecSize;
455 
456         int numShortGaps = shortAxisCells - 1;
457         int numLongGaps = longAxisCells - 1;
458 
459         if (mPortrait) {
460             int vSpaceLeft = heightSpecSize - longAxisStartPadding - longAxisEndPadding
461                     - (cellHeight * longAxisCells);
462             mHeightGap = vSpaceLeft / numLongGaps;
463 
464             int hSpaceLeft = widthSpecSize - shortAxisStartPadding - shortAxisEndPadding
465                     - (cellWidth * shortAxisCells);
466             if (numShortGaps > 0) {
467                 mWidthGap = hSpaceLeft / numShortGaps;
468             } else {
469                 mWidthGap = 0;
470             }
471         } else {
472             int hSpaceLeft = widthSpecSize - longAxisStartPadding - longAxisEndPadding
473                     - (cellWidth * longAxisCells);
474             mWidthGap = hSpaceLeft / numLongGaps;
475 
476             int vSpaceLeft = heightSpecSize - shortAxisStartPadding - shortAxisEndPadding
477                     - (cellHeight * shortAxisCells);
478             if (numShortGaps > 0) {
479                 mHeightGap = vSpaceLeft / numShortGaps;
480             } else {
481                 mHeightGap = 0;
482             }
483         }
484         
485         int count = getChildCount();
486 
487         for (int i = 0; i < count; i++) {
488             View child = getChildAt(i);
489             LayoutParams lp = (LayoutParams) child.getLayoutParams();
490 
491             if (mPortrait) {
492                 lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, shortAxisStartPadding,
493                         longAxisStartPadding);
494             } else {
495                 lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, longAxisStartPadding,
496                         shortAxisStartPadding);
497             }
498             
499             if (lp.regenerateId) {
500                 child.setId(((getId() & 0xFF) << 16) | (lp.cellX & 0xFF) << 8 | (lp.cellY & 0xFF));
501                 lp.regenerateId = false;
502             }
503 
504             int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
505             int childheightMeasureSpec =
506                     MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
507             child.measure(childWidthMeasureSpec, childheightMeasureSpec);
508         }
509 
510         setMeasuredDimension(widthSpecSize, heightSpecSize);
511     }
512 
513     @Override
514     protected void onLayout(boolean changed, int l, int t, int r, int b) {
515         int count = getChildCount();
516 
517         for (int i = 0; i < count; i++) {
518             View child = getChildAt(i);
519             if (child.getVisibility() != GONE) {
520 
521                 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
522 
523                 int childLeft = lp.x;
524                 int childTop = lp.y;
525                 child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
526             }
527         }
528     }
529 
530     @Override
531     protected void setChildrenDrawingCacheEnabled(boolean enabled) {
532         final int count = getChildCount();
533         for (int i = 0; i < count; i++) {
534             final View view = getChildAt(i);
535             view.setDrawingCacheEnabled(enabled);
536             // Update the drawing caches
537             view.buildDrawingCache(true);
538         }
539     }
540 
541     @Override
542     protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
543         super.setChildrenDrawnWithCacheEnabled(enabled);
544     }
545 
546     /**
547      * Find a vacant area that will fit the given bounds nearest the requested
548      * cell location. Uses Euclidean distance to score multiple vacant areas.
549      * 
550      * @param pixelX The X location at which you want to search for a vacant area.
551      * @param pixelY The Y location at which you want to search for a vacant area.
552      * @param spanX Horizontal span of the object.
553      * @param spanY Vertical span of the object.
554      * @param vacantCells Pre-computed set of vacant cells to search.
555      * @param recycle Previously returned value to possibly recycle.
556      * @return The X, Y cell of a vacant area that can contain this object,
557      *         nearest the requested location.
558      */
559     int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
560             CellInfo vacantCells, int[] recycle) {
561         
562         // Keep track of best-scoring drop area
563         final int[] bestXY = recycle != null ? recycle : new int[2];
564         final int[] cellXY = mCellXY;
565         double bestDistance = Double.MAX_VALUE;
566         
567         // Bail early if vacant cells aren't valid
568         if (!vacantCells.valid) {
569             return null;
570         }
571 
572         // Look across all vacant cells for best fit
573         final int size = vacantCells.vacantCells.size();
574         for (int i = 0; i < size; i++) {
575             final CellInfo.VacantCell cell = vacantCells.vacantCells.get(i);
576             
577             // Reject if vacant cell isn't our exact size
578             if (cell.spanX != spanX || cell.spanY != spanY) {
579                 continue;
580             }
581             
582             // Score is center distance from requested pixel
583             cellToPoint(cell.cellX, cell.cellY, cellXY);
584             
585             double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2) +
586                     Math.pow(cellXY[1] - pixelY, 2));
587             if (distance <= bestDistance) {
588                 bestDistance = distance;
589                 bestXY[0] = cell.cellX;
590                 bestXY[1] = cell.cellY;
591             }
592         }
593 
594         // Return null if no suitable location found 
595         if (bestDistance < Double.MAX_VALUE) {
596             return bestXY;
597         } else {
598             return null;
599         }
600     }
601     
602     /**
603      * Drop a child at the specified position
604      *
605      * @param child The child that is being dropped
606      * @param targetXY Destination area to move to
607      */
608     void onDropChild(View child, int[] targetXY) {
609         if (child != null) {
610             LayoutParams lp = (LayoutParams) child.getLayoutParams();
611             lp.cellX = targetXY[0];
612             lp.cellY = targetXY[1];
613             lp.isDragging = false;
614             mDragRect.setEmpty();
615             child.requestLayout();
616             invalidate();
617         }
618     }
619 
620     void onDropAborted(View child) {
621         if (child != null) {
622             ((LayoutParams) child.getLayoutParams()).isDragging = false;
623             invalidate();
624         }
625         mDragRect.setEmpty();
626     }
627 
628     /**
629      * Start dragging the specified child
630      * 
631      * @param child The child that is being dragged
632      */
633     void onDragChild(View child) {
634         LayoutParams lp = (LayoutParams) child.getLayoutParams();
635         lp.isDragging = true;
636         mDragRect.setEmpty();
637     }
638     
639     /**
640      * Computes the required horizontal and vertical cell spans to always 
641      * fit the given rectangle.
642      *  
643      * @param width Width in pixels
644      * @param height Height in pixels
645      */
646     public int[] rectToCell(int width, int height) {
647         // Always assume we're working with the smallest span to make sure we
648         // reserve enough space in both orientations.
649         final Resources resources = getResources();
650         int actualWidth = resources.getDimensionPixelSize(R.dimen.cell_width);
651         int actualHeight = resources.getDimensionPixelSize(R.dimen.cell_height);
652         int smallerSize = Math.min(actualWidth, actualHeight);
653 
654         // Always round up to next largest cell
655         int spanX = (width + smallerSize) / smallerSize;
656         int spanY = (height + smallerSize) / smallerSize;
657 
658         return new int[] { spanX, spanY };
659     }
660 
661     /**
662      * Find the first vacant cell, if there is one.
663      *
664      * @param vacant Holds the x and y coordinate of the vacant cell
665      * @param spanX Horizontal cell span.
666      * @param spanY Vertical cell span.
667      * 
668      * @return True if a vacant cell was found
669      */
670     public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
671         final boolean portrait = mPortrait;
672         final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
673         final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
674         final boolean[][] occupied = mOccupied;
675 
676         findOccupiedCells(xCount, yCount, occupied, null);
677 
678         return findVacantCell(vacant, spanX, spanY, xCount, yCount, occupied);
679     }
680 
681     static boolean findVacantCell(int[] vacant, int spanX, int spanY,
682             int xCount, int yCount, boolean[][] occupied) {
683 
684         for (int x = 0; x < xCount; x++) {
685             for (int y = 0; y < yCount; y++) {
686                 boolean available = !occupied[x][y];
687 out:            for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
688                     for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
689                         available = available && !occupied[i][j];
690                         if (!available) break out;
691                     }
692                 }
693 
694                 if (available) {
695                     vacant[0] = x;
696                     vacant[1] = y;
697                     return true;
698                 }
699             }
700         }
701 
702         return false;
703     }
704 
705     boolean[] getOccupiedCells() {
706         final boolean portrait = mPortrait;
707         final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
708         final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
709         final boolean[][] occupied = mOccupied;
710 
711         findOccupiedCells(xCount, yCount, occupied, null);
712 
713         final boolean[] flat = new boolean[xCount * yCount];
714         for (int y = 0; y < yCount; y++) {
715             for (int x = 0; x < xCount; x++) {
716                 flat[y * xCount + x] = occupied[x][y];
717             }
718         }
719 
720         return flat;
721     }
722 
723     private void findOccupiedCells(int xCount, int yCount, boolean[][] occupied, View ignoreView) {
724         for (int x = 0; x < xCount; x++) {
725             for (int y = 0; y < yCount; y++) {
726                 occupied[x][y] = false;
727             }
728         }
729 
730         int count = getChildCount();
731         for (int i = 0; i < count; i++) {
732             View child = getChildAt(i);
733             if (child instanceof Folder || child.equals(ignoreView)) {
734                 continue;
735             }
736             LayoutParams lp = (LayoutParams) child.getLayoutParams();
737 
738             for (int x = lp.cellX; x < lp.cellX + lp.cellHSpan && x < xCount; x++) {
739                 for (int y = lp.cellY; y < lp.cellY + lp.cellVSpan && y < yCount; y++) {
740                     occupied[x][y] = true;
741                 }
742             }
743         }
744     }
745 
746     @Override
747     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
748         return new CellLayout.LayoutParams(getContext(), attrs);
749     }
750 
751     @Override
752     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
753         return p instanceof CellLayout.LayoutParams;
754     }
755 
756     @Override
757     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
758         return new CellLayout.LayoutParams(p);
759     }
760 
761     public static class LayoutParams extends ViewGroup.MarginLayoutParams {
762         /**
763          * Horizontal location of the item in the grid.
764          */
765         @ViewDebug.ExportedProperty
766         public int cellX;
767 
768         /**
769          * Vertical location of the item in the grid.
770          */
771         @ViewDebug.ExportedProperty
772         public int cellY;
773 
774         /**
775          * Number of cells spanned horizontally by the item.
776          */
777         @ViewDebug.ExportedProperty
778         public int cellHSpan;
779 
780         /**
781          * Number of cells spanned vertically by the item.
782          */
783         @ViewDebug.ExportedProperty
784         public int cellVSpan;
785         
786         /**
787          * Is this item currently being dragged
788          */
789         public boolean isDragging;
790 
791         // X coordinate of the view in the layout.
792         @ViewDebug.ExportedProperty
793         int x;
794         // Y coordinate of the view in the layout.
795         @ViewDebug.ExportedProperty
796         int y;
797 
798         boolean regenerateId;
799 
800         public LayoutParams(Context c, AttributeSet attrs) {
801             super(c, attrs);
802             cellHSpan = 1;
803             cellVSpan = 1;
804         }
805 
806         public LayoutParams(ViewGroup.LayoutParams source) {
807             super(source);
808             cellHSpan = 1;
809             cellVSpan = 1;
810         }
811         
812         public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
813             super(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
814             this.cellX = cellX;
815             this.cellY = cellY;
816             this.cellHSpan = cellHSpan;
817             this.cellVSpan = cellVSpan;
818         }
819 
820         public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
821                 int hStartPadding, int vStartPadding) {
822             
823             final int myCellHSpan = cellHSpan;
824             final int myCellVSpan = cellVSpan;
825             final int myCellX = cellX;
826             final int myCellY = cellY;
827             
828             width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
829                     leftMargin - rightMargin;
830             height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
831                     topMargin - bottomMargin;
832 
833             x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin;
834             y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin;
835         }
836     }
837 
838     static final class CellInfo implements ContextMenu.ContextMenuInfo {
839         /**
840          * See View.AttachInfo.InvalidateInfo for futher explanations about
841          * the recycling mechanism. In this case, we recycle the vacant cells
842          * instances because up to several hundreds can be instanciated when
843          * the user long presses an empty cell.
844          */
845         static final class VacantCell {
846             int cellX;
847             int cellY;
848             int spanX;
849             int spanY;
850 
851             // We can create up to 523 vacant cells on a 4x4 grid, 100 seems
852             // like a reasonable compromise given the size of a VacantCell and
853             // the fact that the user is not likely to touch an empty 4x4 grid
854             // very often 
855             private static final int POOL_LIMIT = 100;
856             private static final Object sLock = new Object();
857 
858             private static int sAcquiredCount = 0;
859             private static VacantCell sRoot;
860 
861             private VacantCell next;
862 
863             static VacantCell acquire() {
864                 synchronized (sLock) {
865                     if (sRoot == null) {
866                         return new VacantCell();
867                     }
868 
869                     VacantCell info = sRoot;
870                     sRoot = info.next;
871                     sAcquiredCount--;
872 
873                     return info;
874                 }
875             }
876 
877             void release() {
878                 synchronized (sLock) {
879                     if (sAcquiredCount < POOL_LIMIT) {
880                         sAcquiredCount++;
881                         next = sRoot;
882                         sRoot = this;
883                     }
884                 }
885             }
886 
887             @Override
888             public String toString() {
889                 return "VacantCell[x=" + cellX + ", y=" + cellY + ", spanX=" + spanX +
890                         ", spanY=" + spanY + "]";
891             }
892         }
893 
894         View cell;
895         int cellX;
896         int cellY;
897         int spanX;
898         int spanY;
899         int screen;
900         boolean valid;
901 
902         final ArrayList<VacantCell> vacantCells = new ArrayList<VacantCell>(VacantCell.POOL_LIMIT);
903         int maxVacantSpanX;
904         int maxVacantSpanXSpanY;
905         int maxVacantSpanY;
906         int maxVacantSpanYSpanX;
907         final Rect current = new Rect();
908 
909         void clearVacantCells() {
910             final ArrayList<VacantCell> list = vacantCells;
911             final int count = list.size();
912 
913             for (int i = 0; i < count; i++) list.get(i).release();
914 
915             list.clear();
916         }
917 
918         void findVacantCellsFromOccupied(boolean[] occupied, int xCount, int yCount) {
919             if (cellX < 0 || cellY < 0) {
920                 maxVacantSpanX = maxVacantSpanXSpanY = Integer.MIN_VALUE;
921                 maxVacantSpanY = maxVacantSpanYSpanX = Integer.MIN_VALUE;
922                 clearVacantCells();
923                 return;
924             }
925 
926             final boolean[][] unflattened = new boolean[xCount][yCount];
927             for (int y = 0; y < yCount; y++) {
928                 for (int x = 0; x < xCount; x++) {
929                     unflattened[x][y] = occupied[y * xCount + x];
930                 }
931             }
932             CellLayout.findIntersectingVacantCells(this, cellX, cellY, xCount, yCount, unflattened);
933         }
934 
935         /**
936          * This method can be called only once! Calling #findVacantCellsFromOccupied will
937          * restore the ability to call this method.
938          *
939          * Finds the upper-left coordinate of the first rectangle in the grid that can
940          * hold a cell of the specified dimensions.
941          *
942          * @param cellXY The array that will contain the position of a vacant cell if such a cell
943          *               can be found.
944          * @param spanX The horizontal span of the cell we want to find.
945          * @param spanY The vertical span of the cell we want to find.
946          *
947          * @return True if a vacant cell of the specified dimension was found, false otherwise.
948          */
949         boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
950             return findCellForSpan(cellXY, spanX, spanY, true);
951         }
952 
953         boolean findCellForSpan(int[] cellXY, int spanX, int spanY, boolean clear) {
954             final ArrayList<VacantCell> list = vacantCells;
955             final int count = list.size();
956 
957             boolean found = false;
958 
959             if (this.spanX >= spanX && this.spanY >= spanY) {
960                 cellXY[0] = cellX;
961                 cellXY[1] = cellY;
962                 found = true;
963             }
964 
965             // Look for an exact match first
966             for (int i = 0; i < count; i++) {
967                 VacantCell cell = list.get(i);
968                 if (cell.spanX == spanX && cell.spanY == spanY) {
969                     cellXY[0] = cell.cellX;
970                     cellXY[1] = cell.cellY;
971                     found = true;
972                     break;
973                 }
974             }
975 
976             // Look for the first cell large enough
977             for (int i = 0; i < count; i++) {
978                 VacantCell cell = list.get(i);
979                 if (cell.spanX >= spanX && cell.spanY >= spanY) {
980                     cellXY[0] = cell.cellX;
981                     cellXY[1] = cell.cellY;
982                     found = true;
983                     break;
984                 }
985             }
986 
987             if (clear) clearVacantCells();
988 
989             return found;
990         }
991 
992         @Override
993         public String toString() {
994             return "Cell[view=" + (cell == null ? "null" : cell.getClass()) + ", x=" + cellX +
995                     ", y=" + cellY + "]";
996         }
997     }
998 }
999 
1000